/**
 * 
 */
package parsetree;

import gui.text.MathDocument;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;

import javax.swing.event.DocumentEvent.ElementChange;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;

import libraries.Library;
import metagrammar.Operator;
import metagrammar.Type;
import parsetree.exceptions.DocumentInitException;
import util.TrieMap;

import com.google.inject.Inject;

/**
 * @author Joshua Lockerman
 * @note Typically throughout this class offset refers to modelOffset ie: the offset within
 *       the document model, while index refers to the location of a child within the childList
 *       of an Element of the tree.
 */
public class ParseTree implements ParsingContext, Serializable, Externalizable {
    
    public class ParseTreeInitException extends RuntimeException {
        ParseTreeInitException(Throwable e)
        {
            super(e);
        }
    }
    
    public static final int DEFAULT_CHILDREN = 3;
    
    public static final Type equivType = new Type("equiv");
    
    private final MathDocument document;
    
    private transient final TrieMap<ElementFactory> prefixMap;
    
    private RootElement root;
    
    @Inject
    public ParseTree(MathDocument mathDocument, Library lib) throws ExecutionException
    {
        if(mathDocument == null) {
            throw new DocumentInitException("Document did not intialize correctly.");
        }
        
        this.document = mathDocument;
        try {
            root = new RootElement(this);
        }
        catch(BadLocationException e) {
            throw new DocumentInitException("Document did not intialize correctly.");
        }
        
        prefixMap = new TrieMap<ElementFactory>();
        Collection<OperatorSpec> specs = lib.loadSpecs();
        for(OperatorSpec spec : specs) {
            prefixMap.put(spec.getLiteral(), spec);
        }
        
        // if no equiv operator are specified use defaults.
        Operator equvilenceOp = new Operator(equivType);
        OperatorSpec spec;
        spec = new OperatorSpec(equvilenceOp, "=", 0);
        // TODO
    }
    
    public MathDocument getDocument()
    {
        return document;
    }
    
    @Override
    public TrieMap<ElementFactory> getPrefixMap()
    {
        return prefixMap;
    }
    
    public List<ElementChange> insertUpdate(Insert change, AttributeSet attributes)
    {
        return root.insertUpdate(change, attributes);
    }
    
    public List<ElementChange> postRemoveUpdate(Removal change)
    {
        return root.postRemoveUpdate(change);
    }
    
    public boolean verify()
    {
        return root.verify();
    }
    
    private void writeObject(ObjectOutputStream out) throws IOException
    {
        writeExternal(out);
    }
    
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
    {
        readExternal(in);
    }
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException
    {
        out.writeObject(document);
        out.writeObject(root);
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
    {
        //TODO
    }
    
    public Element getRoot()
    {
        return root;
    }
    
}
